package com.ego.order.service.impl;

import com.ego.auth.entity.UserInfo;
import com.ego.common.utils.IdWorker;
import com.ego.exception.enums.ExceptionEnum;
import com.ego.exception.exception.EgoException;
import com.ego.item.pojo.Sku;
import com.ego.order.config.WXPayConfiguration;
import com.ego.order.enums.OrderStatusEnum;
import com.ego.order.enums.PayStateEnum;
import com.ego.order.feign.GoodsFeignClient;
import com.ego.order.interceptor.AuthInterceptor;
import com.ego.order.mapper.OrderDetailMapper;
import com.ego.order.mapper.OrderMapper;
import com.ego.order.mapper.OrderStatusMapper;
import com.ego.order.mapper.PayLogMapper;
import com.ego.order.pojo.Order;
import com.ego.order.pojo.OrderDetail;
import com.ego.order.pojo.OrderStatus;
import com.ego.order.pojo.PayLog;
import com.ego.order.properties.PayProperties;
import com.ego.order.service.OrderService;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private OrderDetailMapper orderDetailMapper;

    @Resource
    private OrderStatusMapper orderStatusMapper;

    @Resource
    private PayLogMapper payLogMapper;

    @Resource
    private GoodsFeignClient goodsFeignClient;

    @Resource
    private IdWorker idWorker;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private WXPay wxPay;

    @Resource
    private WXPayConfiguration wxPayConfiguration;

    @Resource
    private PayProperties payProperties;

    private final static String EGO_PAY_CODE_KEY = "ego:pay:code";

    @Override
//    @Transactional
    @GlobalTransactional(name = "submitOrder")
    public Long createOrder(Order order) {
        /**
         * 订单表
         */
        List<OrderDetail> orderDetails = order.getOrderDetails();
        //将orderDetails里面的num单独取出来放在map中
        Map<Long, Integer> numMap = orderDetails.stream().collect(Collectors.toMap(orderDetail -> orderDetail.getSkuId(), orderDetail -> orderDetail.getNum()));
        //获取skuIdList去获取skuList
        List<Long> skuIdList = orderDetails.stream().map(orderDetail -> orderDetail.getSkuId()).collect(Collectors.toList());
        List<Sku> skuList = goodsFeignClient.querySkuListByIdList(skuIdList);
        //计算出总金额，必须从数据库拿price来计算
        BigDecimal totalPrice = new BigDecimal(0L);
        for (int i = 0; i < skuList.size(); i++) {
            Sku sku = skuList.get(i);
            Integer num = numMap.get(sku.getId());
            totalPrice = totalPrice.add(new BigDecimal(sku.getPrice()).multiply(new BigDecimal(num)));
        }
        //设置订单数据
        UserInfo userInfo = AuthInterceptor.getUserInfo();
        order.setUserId(userInfo.getId());
        order.setBuyerNick(userInfo.getUsername());
        order.setTotalPay(totalPrice.longValue());
        order.setActualPay(totalPrice.longValue());
        order.setCreateTime(new Date());
        //生成订单ID
        long orderId = idWorker.nextId();
        order.setOrderId(orderId);
        //保存order
        orderMapper.insert(order);
        /**
         * 订单状态表
         */
        OrderStatus orderStatus = new OrderStatus();
        orderStatus.setOrderId(orderId);
        orderStatus.setStatus(OrderStatusEnum.NO_PAY.getStatus());
        orderStatus.setCreateTime(order.getCreateTime());
        //保存orderStatus
        orderStatusMapper.insert(orderStatus);
        /**
         * 订单详情表
         */
        for (OrderDetail orderDetail : orderDetails) {
            orderDetail.setId(idWorker.nextId());
            orderDetail.setOrderId(orderId);
            orderDetailMapper.insert(orderDetail);//保存orderDetail
        }
        /**
         * 减库存
         */
        List<Map<Object, Object>> list = orderDetails.stream().map(orderDetail -> {
            Map<Object, Object> map = new HashMap<>(2);
            map.put("skuId", orderDetail.getSkuId());
            map.put("num", orderDetail.getNum());
            return map;
        }).collect(Collectors.toList());
        Boolean flag = goodsFeignClient.reduceStock(list);
        if (!flag) {
            EgoException.error(log, ExceptionEnum.STOCK_NOT_ENOUGH);
        }
        /**
         * 异步清空购物车
         */
        return orderId;
    }

    @Override
    public String getPayCodeUrl(Long orderId) {
        String codeUrl = null;
        try {
            String payCodeKey = EGO_PAY_CODE_KEY + orderId;
            //判断redis缓存
            if(stringRedisTemplate.hasKey(payCodeKey)){
                return stringRedisTemplate.opsForValue().get(payCodeKey);
            }

            //通过微信支付SDK统一下单api
            Map<String, String> data = new HashMap<>();
            data.put("body", "腾讯充值中心-QQ会员充值");
            data.put("out_trade_no", orderId.toString());
            data.put("device_info", "");
            data.put("fee_type", "CNY");
            //TODO 从数据库获取订单金额
            data.put("total_fee", "1");
            data.put("spbill_create_ip", "123.12.12.123");
            data.put("notify_url", payProperties.getNotifyUrl());
            data.put("trade_type", "NATIVE");  // 此处指定为扫码支付
            data.put("product_id", "12");

            Map<String, String> resMap = wxPay.unifiedOrder(data);
            //获取签名
            boolean signatureValid = WXPayUtil.isSignatureValid(resMap, this.wxPayConfiguration.getKey(), WXPayConstants.SignType.HMACSHA256);
            //校验签名
            if (!signatureValid) {
                EgoException.error(log,ExceptionEnum.WX_PAY_SIGN_INVALID);
            }
            codeUrl = resMap.get("code_url");
            //存入redis 2小时
            stringRedisTemplate.opsForValue().set(payCodeKey,codeUrl,2, TimeUnit.HOURS);
        }catch (Exception e){
            EgoException.error(log,ExceptionEnum.WX_PAY_CODE_INVALID,e);
        }
        return codeUrl;
    }

    @Override
    @Transactional
    public Integer getOrderStatus(Long orderId) {

        //先查询本地订单状态
        OrderStatus orderStatus = orderStatusMapper.selectById(orderId);
        Integer paySuccess = PayStateEnum.SUCCESS.getValue();//支付成功的状态码 2
        if (orderStatus == null) {
            EgoException.error(log,ExceptionEnum.ORDER_NOT_FOUND);
        }

        if (paySuccess.equals(orderStatus.getStatus())) {
            //已支付
            return paySuccess;
        } else {
            //未支付->调用接口查询状态
            Map<String, String> data = new HashMap<>();
            data.put("out_trade_no", orderId.toString());
            try {
                Map<String, String> resp = wxPay.orderQuery(data);
                String tradeState = resp.get("trade_state");
                //判断返回状态码，返回信息
                if(!resp.get("return_code").equals("SUCCESS")){
                    EgoException.error(log,ExceptionEnum.ORDER_STATUS_EXCEPTION);
                }
                if(!resp.get("result_code").equals("SUCCESS")){
                    EgoException.error(log,ExceptionEnum.ORDER_STATUS_EXCEPTION);
                }
                //获取交易状态
                if(!tradeState.equals("SUCCESS")){
                    EgoException.error(log,ExceptionEnum.ORDER_STATUS_EXCEPTION);
                }

                //已支付->修改订单状态为已支付
                orderStatus.setStatus(paySuccess);
                orderStatusMapper.updateById(orderStatus);
                //添加交易日志
                String timeEndStr = resp.get("time_end");
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
                Date timeEnd = simpleDateFormat.parse(timeEndStr);
                //TODO 最好判断支付金额和订单中金额是否一致
                String totalFee = resp.get("total_fee");
                PayLog payLog = PayLog.builder()
                        .orderId(orderId)
                        .bankType(resp.get("bank_type"))
                        .createTime(new Date())
                        .payTime(timeEnd)
                        .status(paySuccess)
                        .totalFee(Long.valueOf(totalFee))
                        .transactionId(resp.get("transaction_id"))
                        .userId(AuthInterceptor.getUserInfo().getId())
                        .build();
                payLogMapper.insert(payLog);
                return paySuccess;
            } catch (Exception e) {
                EgoException.error(log,ExceptionEnum.ORDER_STATUS_EXCEPTION,e);
            }
        }
        return PayStateEnum.FAIL.getValue();
    }
}
